package org.blue.eventbus;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import rx.Observable;
import rx.Subscription;
import rx.subjects.PublishSubject;
import rx.subjects.SerializedSubject;
import rx.subjects.Subject;

public class EventBus {
	private static EventBus sDefault;

	public static EventBus getDefault() {
		if (sDefault == null) {
			synchronized (EventBus.class) {
				if (sDefault == null) {
					sDefault = new EventBus();
				}
			}
		}
		return sDefault;
	}

	private final Subject<Object, Object> mBus;
	private final AnnotationManager mAnnotationManager;

	public EventBus() {
		mBus = new SerializedSubject<>(PublishSubject.create());
		mAnnotationManager = new AnnotationManager();
	}

	//
	// RxJava wrappers
	//
	public void post(Object event) {
		mBus.onNext(event);
	}

	public <T> Observable<T> recieve(Class<T> eventType) {
		return mBus.ofType(eventType);
	}

	//
	// Annotation methods.
	//
	private final Map<Object, Set<Subscription>> TARGET_HANDLES = new ConcurrentHashMap<>();

	public void bind(Object target) {
		Map<Class<?>, Set<AnnotatedReceiver>> typeReceiverMap = mAnnotationManager.findAllRecievers(target);
		Set<Subscription> handlersSet = new HashSet<>();
		for (Map.Entry<Class<?>, Set<AnnotatedReceiver>> entry : typeReceiverMap.entrySet()) {
			Class<?> eventType = entry.getKey();
			for (AnnotatedReceiver ctx : entry.getValue()) {
				Subscription handler = recieve(eventType).subscribe(event -> {
					unsafeInvoke(ctx, event);
				});
				handlersSet.add(handler);
			}
		}
		unbind(target);
		TARGET_HANDLES.put(target, handlersSet);
	}

	public void unbind(Object target) {
		Set<Subscription> handlersSet = TARGET_HANDLES.remove(target);
		if (handlersSet != null) {
			for (Subscription handler : handlersSet) {
				if (!handler.isUnsubscribed()) {
					handler.unsubscribe();
				}
			}
		}
	}

	private void unsafeInvoke(AnnotatedReceiver ctx, Object event) {
		try {
			ctx.invoke(event);
		} catch (Throwable e) {
			throw new IllegalStateException("Unhandled exception: " + e.getMessage(), e);
		}
	}

}
